package com.nulldev.util.net.proxy;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import com.nulldev.util.java.DataSet;
import com.nulldev.util.java.JVMUtil;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.net.proxy.filtering.ProxyFilterManager;
import com.nulldev.util.threading.v3.RunnableWorker;
import com.nulldev.util.threading.v3.ThreadDispatcher;

public class ProxyServerTask implements Runnable {
	private static final Logger logger = LoggerFactory.getLogger(true, true);
	
	private final String id;
    private final Socket socket;

    public ProxyServerTask(final String id, final Socket socket) {
        this.id = id;
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
        	final InputStream clientInput = new BufferedInputStream(socket.getInputStream());
            final OutputStream clientOutput = socket.getOutputStream();

            final ProxyClientHeader header = ProxyClientHeader.parseFrom(clientInput);
            
            final DataSet<Boolean, String> r = ProxyFilterManager.allowed(header.getURL(), header.getHost());
            
            if (!r.getA()) {
            	logger.debug(r.getB());
            	socket.close();
            }
            
            logger.info(id + " {}", header);

            try (final Socket remoteSocket = new Socket(header.getHost(), header.getPort())) {

                final OutputStream remoteOutput = remoteSocket.getOutputStream();
                final InputStream remoteInput = remoteSocket.getInputStream();

                if (header.isHTTPS()) { // if https, respond 200 to create tunnel, and do not forward header
                    clientOutput.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
                    clientOutput.flush();
                } else { // if http, forward header
                    remoteOutput.write(header.getBytes());
                }

                final Future<?> future = ThreadDispatcher.instance().addFutureTask(
                		RunnableWorker.fromRunnable(new Runnable() {
					
                			@Override
							public void run() {
                				pipe(clientInput, remoteOutput);
                			}
                		}
                ));
                pipe(remoteInput, clientOutput);
                future.get();
            }
        } catch (Exception e) {
        	if (e instanceof NullPointerException) {
        		return;
        	}
            logger.error(id + " gave up", e);
        }
    }

    private void pipe(final InputStream in, final OutputStream out) {
        byte[] buf = new byte[4096];
        int len;
        try {
            while ((len = in.read(buf)) != -1) {
                out.write(buf, 0, len);
            }
            out.flush();
        } catch (IOException ignored) {}
    }
}
